package com.chenfan.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chenfan.gateway.common.utils.TraceUtil;
import com.chenfan.gateway.state.GatewayState;
import com.chenfan.gateway.vo.BaseUserVO;
import com.google.common.collect.Maps;
import com.sun.jndi.toolkit.url.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author lizhejin
 */
@Component
@RefreshScope
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {

    public static final String TOKEN_REDIS_KEY = "userToken";

    private static final String AUTHORIZATION_TOKEN = "Authorization";
    private static final String PRIVILEGE_CODE = "privilegeCode";
    private static final String currentUser = "currentUser";

    private static final String USER_ID = "userId";
    private static final String TENANT_ID = "tenantId";
    private static final String APP_KEY = "app_key";
//    private final int MAX_CACHE_SIZE = 1000;
    private final Logger log = LoggerFactory.getLogger(AuthorizeGatewayFilterFactory.class);
    @Value("${noTokenUrls}")
    private String noTokenUrls;

    @Value("${notRefreshTokenUrls:/baseUpdateVersion/getVersion}")
    private String notRefreshTokenUrls;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            TraceUtil.logMDC(exchange, TraceUtil.TRACE_ID, TraceUtil.getUUID());
            ServerHttpRequest serverHttpRequest = exchange.getRequest();
            InetSocketAddress remoteAddress = serverHttpRequest.getRemoteAddress();
            InetAddress address = remoteAddress.getAddress();
            String hostAddress = address.getHostAddress();
            String hostName = remoteAddress.getHostName();
            Integer port = remoteAddress.getPort();
            URI uri = serverHttpRequest.getURI();
            String token = exchange.getRequest().getHeaders().getFirst(AUTHORIZATION_TOKEN);
            String appKey = exchange.getRequest().getHeaders().getFirst(APP_KEY);

            log.info("请求参数address:{},hostName:{},port:{},URI:{}", hostAddress, hostName, port, uri);
            Long userId = getUserId(token, exchange);

            if (userId == null) {
                //如果url中含有如下 则直接通过
                if(Objects.nonNull(noTokenUrls)){
                    List<String> list = Arrays.asList(noTokenUrls.split(","));
                    if (list.contains(uri.getRawPath().replaceFirst("\\/", ""))) {
                        return chain.filter(exchange);
                    }
                }
                log.error("验证不通过,token不存在!token=" + token);
                return unauthorized(exchange, GatewayState.UNAUTHORIZED.getCode());
            } else if (userId == 0L) {
                return unauthorized(exchange, GatewayState.UN_AUTH.getCode());
            }

            // 透传用户信息
            try {
                TraceUtil.logMDC(exchange,USER_ID,Objects.isNull(userId) ? "" : userId.toString());
                String userJson = stringRedisTemplate.opsForValue().get(TOKEN_REDIS_KEY + ":" + token);
                if (StringUtils.hasText(userJson)){
                    BaseUserVO baseUserVO = JSONObject.parseObject(userJson, BaseUserVO.class);
                    TraceUtil.logMDC(exchange,TENANT_ID,Objects.isNull(baseUserVO.getTenantId()) ? "" : ""+baseUserVO.getTenantId());
                    serverHttpRequest = exchange.getRequest().mutate().header(currentUser, URLEncoder.encode(JSONObject.toJSONString(baseUserVO), "UTF-8")).build();
                    continueToken(token,appKey,uri);
                }
            } catch (Exception e) {
                log.error("用户信息设置错误", e);
            }

            return chain.filter(exchange.mutate().request(serverHttpRequest).build());
        };
    }

    /**
     *  续token
     * @param token
     */
    private void continueToken(String token,String appKey,URI uri){
        //不刷新token值
        if(Objects.nonNull(notRefreshTokenUrls)){
            List<String> list = null;
            if(notRefreshTokenUrls.contains(",")){
                list = Arrays.asList(notRefreshTokenUrls.split(","));
            }else {
                list = Arrays.asList(notRefreshTokenUrls);
            }
            if (list.contains(uri.getRawPath().replaceFirst("\\/", ""))) {
                return;
            }
        }
        String time = stringRedisTemplate.opsForValue().get("login:time");
        stringRedisTemplate.boundValueOps(TOKEN_REDIS_KEY + ":" + token).expire(Objects.nonNull(time)? (long)(Double.valueOf(time)*60*60): 24*60*60, TimeUnit.SECONDS);
        if(Objects.nonNull(appKey)){
            stringRedisTemplate.boundValueOps(TOKEN_REDIS_KEY + ":" + token + ":resourceList:" + appKey).expire(Objects.nonNull(time)? (long)(Double.valueOf(time)*60*60): 24*60*60, TimeUnit.SECONDS);
        }
        // todo leo privilegecode续签待定
//        stringRedisTemplate.boundValueOps(TOKEN_REDIS_KEY + ":" + token: ).expire(Objects.nonNull(time)? Double.valueOf(time).longValue(): 24, TimeUnit.HOURS);
    }

    private String getResourceKey(String token,String privilegeCode){
        return TOKEN_REDIS_KEY + ":" + token + ":searchPrivileges:" + privilegeCode;
    }


    private Long getUserId(String token, ServerWebExchange exchange) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        String privilegeCode = exchange.getRequest().getHeaders().getFirst(PRIVILEGE_CODE);

        JSONObject user = loadUser(token);
        if (user == null) {
            return null;
        }
        Long userId = user.getLong("userId");
        String realName = user.getString("realName");

        if (StringUtils.isEmpty(privilegeCode) || "admin".equals(realName)) {
            return userId;
        }

        JSONObject privilegeRes = JSONObject.parseObject(stringRedisTemplate.opsForValue().get(getResourceKey(token,privilegeCode)));

        if (Objects.nonNull(privilegeRes)) {
            Boolean disableStatus = privilegeRes.getBoolean("disableStatus");
            if (!disableStatus) {
                return 0L;
            }
        }
        return userId;

    }

    private JSONObject loadUser(String key) {
        JSONObject userVO = JSONObject.parseObject(stringRedisTemplate.opsForValue().get(TOKEN_REDIS_KEY + ":" + key));
        return userVO;
    }


    private Mono<Void> unauthorized(ServerWebExchange serverWebExchange, Integer code) {
        Map<String, Object> map = Maps.newHashMap();
        map.put("code", code);
        map.put("message", GatewayState.getMsgByCode(code));
        DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(JSON.toJSONBytes(map));
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }

}
